/*
 *  Copyright 2014 The WebRTC Project Authors. All rights reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

package com.cloudminds.resttest.service;

import com.cloudminds.resttest.webrtc.IceCandidate;
import com.cloudminds.resttest.webrtc.SessionDescription;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

/**
 * Negotiates signaling for the communication with Assistant Server.
 */
public class WebSocketRTCClient implements AppRTCClient, WebSocketChannelClient.WebSocketChannelEvents
{
	private static final String TAG = "HS/WSRTCClient";
	
	private enum ConnectionState
	{
		NEW,
		CONNECTING,
		CONNECTED,
		CLOSED,
		ERROR
	}
	
	;
	
	private        SignalingEvents               events;
	private static WebSocketChannelClient        wsClient;
	private        ConnectionState               wsState;
	private        AssistantConnectionParameters connectionParameters;
	
	public WebSocketRTCClient(SignalingEvents events)
	{
		this.events = events;
		wsState = ConnectionState.NEW;
	}
	
	public AssistantConnectionParameters getConnectionParameters()
	{
		return connectionParameters;
	}
	
	/**
	 * Connect to Assistant Server using given connection parameters.
	 */
	@Override
	public void connectSignalServer(AssistantConnectionParameters connectionParameters, final String sessionId)
	{
		this.connectionParameters = connectionParameters;
		System.out.println("To call connectToAssistantServerInternal asynchronously");
		
		connectToAssistantServerInternal(sessionId);
	}
	
	@Override
	public void rebindSignalServer(final String sessionid)
	{
		//        wsState = ConnectionState.NEW;
		wsState = ConnectionState.CONNECTING;
		wsClient.rebind(sessionid);
	}
	
	@Override
	public void reconnectSignalServer(AssistantConnectionParameters connectionParameters, final String sessionId)
	{
		
		this.connectionParameters = connectionParameters;
		System.out.println("on reconnectSignalServer entry!");
		
		if (wsState == ConnectionState.CONNECTING)
		{
			System.out.println("Ignore to reconnect when it is connecting");
			return;
		}
		
		if (wsClient != null)
		{
			wsClient.disconnect(false);
			wsClient = null;
		}
		connectToAssistantServerInternal(sessionId);
		
	}
	
	/**
	 * Disconnect from Assistant Server.
	 */
	@Override
	public void disconnectFromSignalServer()
	{
		
		disconnectFromAssistantServerInternal();
		
	}
	
	/**
	 * Connect to Assistant Server - function runs on a local looper thread.
	 */
	private void connectToAssistantServerInternal(final String sessionId)
	{
		String connectionUrl = connectionParameters.wssUrl;
		String name = connectionParameters.username;
		String password = connectionParameters.password;
		System.out.println("Connect to WebSocket Server: " + connectionUrl + " sessionid:" + sessionId);
		if (wsState == ConnectionState.CONNECTING)
		{
			System.out.println("Ignore to reconnect when it is connecting to WebSocket Server: " + connectionUrl);
		}
		if (wsClient != null)
		{
			
			if (wsState == ConnectionState.CONNECTED)
			{
				System.out.println("WebSocket is connected,so ignore to login");
				events.onSignalServerConnected(null);
			}
			else
			{
				wsClient.disconnect(true);
				wsClient = null;
			}
		}
		wsState = ConnectionState.CONNECTING;
		
		wsClient = new WebSocketChannelClient(this);
		wsClient.setConnectionParameters(connectionParameters);
		
		wsClient.connect(connectionUrl, name, password, sessionId);
	}
	
	public boolean isConnecting()
	{
		if (wsState == ConnectionState.NEW)
		{
			return true;
		}
		else
		{
			return false;
		}
	}
	
	public boolean isWsConnected()
	{
		return wsState == ConnectionState.CONNECTED;
	}
	
	/**
	 * Disconnect from Assistant Server - function runs on a local looper thread.
	 */
	private void disconnectFromAssistantServerInternal()
	{
		System.out.println("Disconnect from WebSocket ServerRoom with state: " + wsState);
		wsState = ConnectionState.CLOSED;
		if (wsClient != null)
		{
			wsClient.disconnect(true);
			wsClient = null;
		}
	}
	
	/**
	 * Helper functions to get websocket server url and username.
	 */
	//    private String getConnectionUrl(AssistantConnectionParameters connectionParameters) {
	//        return connectionParameters.wssUrl;
	//    }
	//
	//    private String getRegisterName(AssistantConnectionParameters connectionParameters) {
	//        return connectionParameters.username;
	//    }
	
	/**
	 * Send local offer SDP to the other participant.
	 */
	@Override
	public String sendCallStart(final String sessionId, final SessionDescription sdp)
	{
		
		try
		{
			if (null == wsClient)
			{
				System.out.println("Send callstart,bt wsClient is null");
				return "end callstart,bt wsClient is null";
			}
			JSONObject json = new JSONObject();
			jsonPut(json, "id", "callStart");
			if (!sessionId.isEmpty())
			{
				jsonPut(json, "sid", sessionId);
			}
			jsonPut(json, "from", connectionParameters.username);
			jsonPut(json, "helper", connectionParameters.customer);
			jsonPut(json, "sdpOffer", sdp.description);
			jsonPut(json, "token", wsClient.getToken());
			jsonPut(json, "rcuId", "");
			jsonPut(json, "urgent", "hi@cloudminds.com");
			jsonPut(json, "appType", connectionParameters.robotType);
			jsonPut(json, "role", "client");
			jsonPut(json, "destroyMedia", connectionParameters.destroyMedia);
			System.out.println("run: sendcallStart \n " + json.toString());
			wsClient.send(json.toString());
			return "调用成功";
		}
		catch (Exception e)
		{
			e.printStackTrace();
			return "调用失败";
		}
	}
	
	public static void sendMessage(final String json, WebSocketChannelClient wsClient1)
	{
		
		System.out.println("run: sendMessage : " + json.toString());
		wsClient1 = wsClient;
		System.out.print(wsClient);
		if (null == wsClient1)
		{
			System.out.println("wsClient is null, return");
			return;
		}
		wsClient1.send("textType" + json);
		
	}
	
	@Override
	public void getStats()
	{
		
		JSONObject json = new JSONObject();
		jsonPut(json, "id", "getStats");
		wsClient.send(json.toString());
		
	}
	
	@Override
	public void sendInfo(final String sessionId, final JSONObject infoData)
	{
		
		if (null == wsClient)
		{
			System.out.println("wsClient is null, return");
			return;
		}
		if (!isWsConnected())
		{
			try
			{
				if (infoData.getString("type").equalsIgnoreCase("reportStatus"))
				{
					System.out.println("Ready to send reportStatus,but ws is not connected");
					return;
				}
			}
			catch (JSONException e)
			{
				return;
			}
		}
		
		if (!infoData.has("id"))
		{
			jsonPut(infoData, "id", "info");
		}
		
		if (!sessionId.isEmpty())
		{
			jsonPut(infoData, "sid", sessionId);
		}
		jsonPut(infoData, "from", connectionParameters.username);
		String info = infoData.toString();
		System.out.println("run: sendInfo " + info);
		wsClient.send(info);
		
	}
	
	@Override
	public void sendLocation(final String sessionId)
	{
	
	}
	
	@Override
	public void responseConnect(final String sessionId)
	{
		//        executor.execute(new Runnable() {
		//            @Override
		//            public void run() {
		//                JSONObject json = new JSONObject();
		//                jsonPut(json, "id", "info");
		//                if(!TextUtils.isEmpty(sessionId)){
		//                    jsonPut(json, "sid", sessionId);
		//                }
		//                jsonPut(json, "from", connectionParameters.username);
		//                jsonPut(json, "to", customer);
		//                jsonPut(json, "type", "connect");
		//                jsonPut(json, "data", "response");
		//                wsClient.send(json.toString());
		//            }
		//        });
	}
	
	/*
	 * 与客服的心跳
	 * infoData
	 * {
	 *   pepperStatus:0/1    客户端与pepper连接状态 0:未连接 1:已连接
	 *   asrStatus:0/1/2   语音识别状态 0:为开启 1:正常 2:异常
	 * }
	 */
	@Override
	public void sendHariInfo(final String sessionId, final String infoData)
	{
		//        executor.execute(new Runnable() {
		//            @Override
		//            public void run() {
		//                JSONObject json = new JSONObject();
		//                jsonPut(json, "id", "info");
		//                if(!TextUtils.isEmpty(sessionId)){
		//                    jsonPut(json, "sid", sessionId);
		//                }
		//                jsonPut(json, "from", connectionParameters.username);
		//                jsonPut(json, "to", customer);
		//                jsonPut(json, "type", "hariInfo");
		//                jsonPut(json, "data", infoData);
		//                wsClient.send(json.toString());
		//            }
		//        });
	}
	
	/**
	 * Send Ice candidate to the other participant.
	 */
	@Override
	public void sendCallUpdate(final String sessionId, final IceCandidate candidate)
	{
		
		if (null == wsClient)
		{
			System.out.println("wsClient is null, return");
			return;
		}
		
		JSONObject json = new JSONObject();
		
		jsonPut(json, "id", "callUpdate");
		jsonPut(json, "sid", sessionId);
		jsonPut(json, "from", connectionParameters.username);
		jsonPut(json, "role", "client");
		if (candidate != null)
		{
			JSONObject candidateJson = new JSONObject();
			
			jsonPut(candidateJson, "candidate", candidate.sdp);
			jsonPut(candidateJson, "sdpMid", candidate.sdpMid);
			jsonPut(candidateJson, "sdpMLineIndex", candidate.sdpMLineIndex);
			
			jsonPut(json, "candidate", candidateJson);
		}
		System.out.println("run: sendCallUpdate");
		wsClient.send(json.toString());
	}
	
	@Override
	public void sendHeartbeat(final String sessionId)
	{
		
		if (null == wsClient)
		{
			//System.out.println("wsClient is null, return");
			return;
		}
		
		JSONObject json = new JSONObject();
		
		jsonPut(json, "id", "heartbeat");
		jsonPut(json, "sid", sessionId);
		jsonPut(json, "from", connectionParameters.username);
		jsonPut(json, "role", "client");
		//System.out.println("run: sendHeartbeat");
		wsClient.send(json.toString());
	}
	
	@Override
	public void sendCallStop(final String sessionId)
	{
		//Toast.makeText(BaseApplication.getInstance(),"send CallStop 1",Toast.LENGTH_LONG).show();
		
		if (null == wsClient)
		{
			System.out.println("wsClient is null, return");
			return;
		}
		JSONObject json = new JSONObject();
		JSONObject candidateJson = new JSONObject();
		
		jsonPut(json, "id", "callStop");
		jsonPut(json, "sid", sessionId);
		jsonPut(json, "token", wsClient.getToken());
		jsonPut(json, "from", connectionParameters.username);
		
		System.out.println("run: sendCallStop");
		wsClient.send(json.toString());
	}
	
	@Override
	public void logout(final String sessionId)
	{
		JSONObject json = new JSONObject();
		try
		{
			json.put("id", "logout");
			json.put("name", connectionParameters.username);
			json.put("sid", sessionId);
			json.put("role", "client");
			json.put("password", connectionParameters.password);
			
		}
		catch (JSONException e)
		{
			System.out.println("WebSocket register JSON error: " + e.getMessage());
			return;
		}
		
		System.out.println("run: sendLogout " + json.toString());
		wsClient.send(json.toString());
		
	}
	
	/**
	 * WebSocketChannelEvents interface implementation.
	 * <p>
	 * All events are called by WebSocketChannelClient on a local looper thread
	 * (passed to WebSocket client constructor).
	 */
	@Override
	public JSONArray onWebSocketMessage(final String msg)
	{
		//        if (wsClient.getState() != WebSocketConnectionState.REGISTERED) {
		//            Log.e(TAG, "Got WebSocket message in non registered state.");
		//            return;
		//        }
		JSONArray answer = new JSONArray();
		try
		{
			JSONObject json = new JSONObject(msg);
			String msgId = json.getString("id");
			
			//            if (msgId.equals("registerResponse")) {
			//                String response = json.getString("response");
			//                if (response.equals("rejected")) {
			//                    //this case will be handled in websocketChannelClient
			//                    String detail = json.getString("message");
			//                    reportError("Failed to register: " + detail);
			//                } else {
			//                    Log.d(TAG, "Register to hari server completed.");
			//                    wsState = ConnectionState.CONNECTED;
			//                    events.onSignalServerConnected();
			//                }
			//            }
			//            else
			
			//events.onChannelSignalError("WebSocket message JSON parsing error: " + e.toString());
			
			if (msgId.equals("callResponse"))
			{
				
				if (json.has("response"))
				{
					String response = json.getString("response");
					if (response.equalsIgnoreCase("ongoing"))
					{
						//indicates the call session is created in server side but not accepted by HI
						String sid = json.getString("sid");
						if (!sid.isEmpty()) { events.onCallSessionCreated(sid); }
						else { events.onChannelSignalError("Invalid sessionId within callStartResponse"); }
					}
					else if (response.equals("rejected"))
					{
						String detail = json.getString("message");
						if (detail.equals("Busy, no free helper"))
						{
							events.onChannelCallRejected(detail);
						}
						else if (detail.equals("Invalid session id"))
						{
							events.onChannelCallRejected("服务已结束");
						}
						else
						{
							events.onChannelCallRejected("Failed to call for assistance: " + detail);
						}
					}
					else
					{ //accepted
						
						String sid = json.getString("sid");
						events.onCallSessionCreated(sid);
						
						if (json.has("sdpAnswer"))
						{
							String answer1 = json.getString("sdpAnswer");
							System.out.println("Received Sdp Answer: " + answer1);
							SessionDescription sdpAnswer = new SessionDescription(SessionDescription.Type.fromCanonicalForm("answer"), answer1);
							events.onCallAccepted(sdpAnswer);
						}
					}
				}
				else
				{
					String reason = json.getString("reason");
					//                    if(reason.equals("reject")){
					if (reason.equals("reject") && json.getString("message").equals("User declined")) { events.onChannelSignalError(reason); }
					else if (reason.equals("reject") && json.getString("message").equals("User busy"))
					{
						events.onChannelSignalError(reason);
					}
					else
					{
						events.onChannelSignalError(json.getString("message"));
					}
				}
			}
			else if (msgId.equals("callUpdate"))
			{
				//String peerName = json.getString("peer");
				IceCandidate candidate = null;
				if (json.has("candidate"))
				{
					JSONObject serverCandidate = json.getJSONObject("candidate");
					System.out.println("IceCandidate Message Received: " + serverCandidate.getString("candidate") + ", " + serverCandidate.getString("sdpMid") + ", " + serverCandidate.getString("sdpMLineIndex"));
					candidate = new IceCandidate(serverCandidate.getString("sdpMid"), serverCandidate.getInt("sdpMLineIndex"), serverCandidate.getString("candidate"));
				}
				String sid = json.getString("sid");
				events.onCallUpdate(sid, candidate);
			}
			else if (msgId.equals("callUpdateResponse"))
			{
				//String peerName = json.getString("peer");
				IceCandidate candidate = null;
				String sid = json.getString("sid");
				events.onCallUpdate(sid, candidate);
			}
			else if (msgId.equals("heartbeatResponse"))
			{
				String sid = json.getString("sid");
				events.onHeartbeatResponse(sid);
			}
			else if (msgId.equals("callStop"))
			{
				String detail = "";
				if (json.has("message")) { detail = json.getString("message"); }
				String sid = json.getString("sid");
				events.onCallStop(sid, detail);
			}
			else if (msgId.equals("objectIdentified"))
			{
				String objectName = json.getString("name");
				if (objectName != null && objectName.length() > 0)
				{
					events.onObjectIdentified(objectName);
				}
			}
			else if (msgId.equals("faceIdentified"))
			{
				String faceName = json.getString("name");
				if (faceName != null && faceName.length() > 0)
				{
					events.onFaceIdentified(faceName);
				}
			}
			else if (msgId.equals("navigationCommand"))
			{
                /*TODO
                type：0 代表模式切换
                command：1.人工模式；0.ai模式 （int）

                type：1 代表平移
                command：L 左 R 右 F 前进... （String）
                value：代表具体的数值。

                type：2 代表旋转
                command：代表旋转的角度 （double）
                 */
				String command = json.getString("command");
				if (command != null && command.length() > 0)
				{
					events.onCommandReceived(json.toString());
					//                    events.onCommandReceived(command);
				}
				System.out.println("naviCommand:" + json.toString());
			}
			else if (msgId.equals("slamNavigationStart"))
			{
				String startPoint = json.getString("startingPoint");
				String finishingPoint = json.getString("finishingPoint");
				if (startPoint != null && startPoint.length() > 0 && finishingPoint != null && finishingPoint.length() > 0)
				{
					events.onSlamNavigationStart(startPoint, finishingPoint);
				}
			}
			else if (msgId.equals("info"))
			{
				
				System.out.println("ws: onInfo" + json);
				
				answer = events.onInfo(json);
				
			}
		}
		catch (JSONException e)
		{
			events.onChannelSignalError("WebSocket message JSON parsing error: " + e.toString());
		}
		return answer;
	}
	
	@Override
	public void onWebSocketClose()
	{
		System.out.println("onWebSocketClose");
		wsState = ConnectionState.CLOSED;
		events.onChannelClose();
	}
	
	@Override
	public void onWebSocketConnectError(final String errorMessage)
	{
		System.out.println(errorMessage);
		
		if (wsState != ConnectionState.ERROR)
		{
			wsState = ConnectionState.ERROR;
		}
		events.onChannelConnectionError(errorMessage);
	}
	
	@Override
	public void onWebSocketRegisterError(final String errorMessage)
	{
		System.out.println(errorMessage);
		
		if (wsState != ConnectionState.ERROR)
		{
			wsState = ConnectionState.ERROR;
		}
		events.onChannelAuthenticationError(errorMessage);
	}
	
	@Override
	public void onWebSocketRegisterred(final JSONObject data)
	{
		wsState = ConnectionState.CONNECTED;
		events.onSignalServerConnected(data);
		
	}
	
	// Put a |key|->|value| mapping in |json|.
	private static void jsonPut(JSONObject json, String key, Object value)
	{
		try
		{
			json.put(key, value);
		}
		catch (JSONException e)
		{
			throw new RuntimeException(e);
		}
	}
}
